function createElement(tag, props = {}, children = []) {
  return { tag, props, children };
}

function render(vnode) {
  if (typeof vnode === 'string') {
    return document.createTextNode(vnode);
  }

  const element = document.createElement(vnode.tag);

  Object.keys(vnode.props).forEach(key => {
    element.setAttribute(key, vnode.props[key]);
  });

  vnode.children.forEach(child => {
    element.appendChild(render(child));
  });

  return element;
}

function patch(oldNode, newNode) {
  if (oldNode.tag !== newNode.tag) {
    oldNode.parentNode.replaceChild(render(newNode), oldNode);
    return;
  }

  if (typeof newNode === 'string') {
    if (oldNode.textContent !== newNode) {
      oldNode.textContent = newNode;
    }
    return;
  }

  const oldChildren = oldNode.children;
  const newChildren = newNode.children;
  const oldKeyToIdx = createKeyToOldIdx(oldChildren);

  for (let i = 0; i < newChildren.length; i++) {
    const newChild = newChildren[i];
    const oldKeyIndex = oldKeyToIdx[newChild.key];
    if (oldKeyIndex !== undefined) {
      const oldChild = oldChildren[oldKeyIndex];
      patch(oldChild, newChild);
      oldChildren[oldKeyIndex] = undefined;
    } else {
      oldNode.appendChild(render(newChild));
    }
  }

  for (let i = 0; i < oldChildren.length; i++) {
    if (oldChildren[i] !== undefined) {
      oldNode.removeChild(oldChildren[i]);
    }
  }
}

function createKeyToOldIdx(children) {
  const map = {};
  for (let i = 0; i < children.length; i++) {
    const key = children[i].key;
    if (key !== undefined) {
      map[key] = i;
    }
  }
  return map;
}

export { createElement, render, patch };